<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta
      name="viewport"
      content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no, viewport-fit=cover"
    />
    <meta http-equiv="X-UA-Compatible" content="ie=edge" />
    <title>Dash and Blast prototype</title>
    <link rel="stylesheet" href="/css/examples.css?ver=1.0.0" />
    <script src="/js/examples.js?ver=1.1.1"></script>
    <script src="/lib/phaser.min.js?ver=3.52.0"></script>
    <script src="/lib/enable3d/enable3d.phaserExtension.0.22.0.min.js"></script>
  </head>

  <body>
    <script>
      const { enable3d, Scene3D, Canvas, THREE } = ENABLE3D

      const addPlayer = scene => {
        const player = scene.third.physics.add.cylinder({
          z: 10,
          y: 1,
          radiusSegments: 16,
          radiusTop: 1,
          radiusBottom: 1,
          collisionFlags: 0
        })
        player.body.setAngularFactor(0, 0, 0)
        player.body.setRestitution(0.25)
        // https://docs.panda3d.org/1.10/python/programming/physics/bullet/ccd
        // https://github.com/chandlerprall/Physijs/wiki/Collisions#motion-clamping
        player.body.setCcdMotionThreshold(1e-2)
        player.body.setCcdSweptSphereRadius(0.8)

        return player
      }

      class Goal {
        activate() {
          this.active = true

          if (!this.object.body) {
            this.scene.third.physics.add.existing(this.object, { autoCenter: false })

            this.object.body.setAngularVelocityY(this.speed)
            this.object.body.setLinearFactor(0, 0, 0)
            this.object.body.setAngularFactor(0, 1, 0)
            this.object.body.setFriction(0)
            this.object.body.setCollisionFlags(2)
            this.object.body.setRestitution(1)
            this.object.body.setCollisionFlags(0)
            this.object.body.setAngularVelocityY(this.speed)
          }
        }

        deactivate() {
          this.active = false
          if (this.object.body) this.scene.third.physics.destroy(this.object.body)
        }

        constructor(scene, centerX, centerZ, index) {
          this.scene = scene
          this.centerX = centerX
          this.centerZ = centerZ
          this.index = index

          this.start = Math.random() * Math.PI * 2
          this.length = (Math.random() * Math.PI) / 2 + Math.PI / 2
          this.radius = Math.random() * 3 + 4
          this.speed = Math.random() * 2 + 2

          const addCurve = (x = 0, z = 0, speed = 1, length = 3.16, radius = 4, width = 1, depth = 1) => {
            const curve = new THREE.Shape()
            curve.arc(0, 0, radius, 0, length, false)
            let currentPoint = curve.getPoint(1)
            curve.arc(-currentPoint.x, -currentPoint.y, radius - width, length, 2 * Math.PI, true)

            let object = scene.third.add.extrude(
              {
                y: 1,
                shape: curve,
                curveSegments: Math.round(length * 2),
                depth: depth,
                bevelEnabled: false
              },
              { lambert: { color: 0xcccccc } }
            )

            // offset the center of mass (before adding a physical body)
            // add center to one corner (the corner where the circle starts)
            object.geometry.computeBoundingBox()
            object.geometry.translate(-object.geometry.boundingBox.max.x, object.geometry.boundingBox.max.y, 0)
            // offset the center by the radius
            object.geometry.translate(radius, 0, 0)

            object.rotateX(Math.PI / 2)
            object.position.set(x, 0, z)

            object.shape = 'concave'

            return object
          }

          this.object = addCurve(this.centerX, this.centerZ, this.speed, this.length, this.radius)

          if (this.index === 0 || this.index === 1) this.activate()

          // the cylinder in the middle
          scene.third.add.cylinder(
            {
              height: 0.2,
              x: centerX,
              y: -0.2,
              z: centerZ,
              radiusTop: 0.95,
              radiusBottom: 0.95,
              collisionFlags: 4,
              radiusSegments: 32
            },
            { lambert: { color: 0xcccccc } }
          )
        }
      }

      class DashAndBlast extends Scene3D {
        init() {
          this.accessThirdDimension()
          this.goals = []
          delete this.player
          this.goalIndex = 0
          this.ready = false
        }

        create() {
          this.third.warpSpeed('camera', 'sky', 'light', 'lookAtCenter')

          // this.third.physics?.debug?.enable()

          for (let i = 0; i < 10; i++) {
            this.goals.push(new Goal(this, Phaser.Math.RND.integerInRange(-6, 6), i * -20, i))
          }

          this.player = addPlayer(this)

          this.third.physics.add.ground({ width: 35, height: 220, y: -1, z: -95 }, { lambert: { color: 0x95a7ef } })

          this.input.on('pointerdown', pointer => {
            const { x: vx, y: vy, z: vz } = this.player.body.velocity
            if (Math.abs(vx) > 1 || Math.abs(vy) > 1 || Math.abs(vz) > 1) return

            this.player.body.ammo.setMassProps(1, new Ammo.btVector3(1, 1, 1))
            const speed = 75
            // apply force in direction of the next goal
            const pos = this.player.position
            const goal = this.goals[this.goalIndex]
            const angle = Math.atan2(goal.centerX - pos.x, goal.centerZ - pos.z)

            const x = Math.sin(angle) * speed,
              y = this.player.body.velocity.y,
              z = Math.cos(angle) * speed

            this.player.body.setVelocity(x, y, z)
          })
          this.ready = true
        }

        update(time, delta) {
          if (!this.ready) return

          // camera follow
          const pos = this.player.position.clone()
          this.third.camera.position.set(pos.x, pos.y + 20, pos.z + 20)
          this.third.camera.lookAt(pos.x, pos.y, pos.z - 5)

          // update player
          if (this.player.position.y < -10) this.scene.restart()
          if (this.player.position.z - 1 <= this.goals[this.goalIndex].centerZ) {
            this.player.body.setVelocity(0, 0, 0)
            this.player.body.ammo.setMassProps(100, new Ammo.btVector3(1, 1, 1))

            this.player.body.setCollisionFlags(2)

            // set the player at the center of the goal
            this.player.position.x = this.goals[this.goalIndex].centerX
            this.player.position.z = this.goals[this.goalIndex].centerZ
            this.player.body.needUpdate = true

            this.time.addEvent({
              delay: 50,
              callback: () => this.player.body.setCollisionFlags(0)
            })

            if (this.goalIndex < this.goals.length - 1) {
              this.goalIndex++
              this.goals.forEach((goal, i) => {
                if (this.goalIndex === i || this.goalIndex === i + 1 || this.goalIndex === i - 1) goal.activate()
                else goal.deactivate()
              })
            }
          }
        }
      }

      const config = {
        type: Phaser.WEBGL,
        transparent: true,
        scale: {
          mode: Phaser.Scale.FIT,
          autoCenter: Phaser.Scale.CENTER_BOTH,
          width: window.innerWidth * Math.max(1, window.devicePixelRatio / 2),
          height: window.innerHeight * Math.max(1, window.devicePixelRatio / 2)
        },
        scene: [DashAndBlast],
        ...Canvas()
      }

      window.addEventListener('load', () => {
        enable3d(() => new Phaser.Game(config)).withPhysics('/lib/ammo/kripken')
      })
    </script>
  </body>
</html>
